﻿using System.Collections.Generic;
using SymbolIntegration.core;
using SymbolIntegration.core.Expressions;
using SymbolIntegration.core.Expressions.Functions;
using SymbolIntegration.core.Helpers;

namespace AnalyticIntegration.DefaultIntergators
{
    public class ExpSinIntegrator : IIntegrator
    {
        public bool CanIntegrate(List<Expression> multipliers, ExpressionStatistic statistic, string variable)
        {
            bool isCan = false;
            do
            {
                if (statistic.Functions.Count != 2 || statistic.HasUnpolinomicPowers || statistic.HasUnintegratedExpression)
                {
                    break;
                }

                statistic.Functions.Sort((fstatistic1, fstatistic2) => fstatistic1.Type.CompareTo(fstatistic2.Type));
                var expStatistic = statistic.Functions[0];
                var sinCosStatistic = statistic.Functions[1];
                if (expStatistic.ArgumentPolinomMaxPower == null || expStatistic.ArgumentPolinomMaxPower != 1 ||
                    expStatistic.Type != FunctionType.Exp)
                {
                    break;
                }

                if (sinCosStatistic.ArgumentPolinomMaxPower == null || sinCosStatistic.ArgumentPolinomMaxPower != 1 ||
                    (sinCosStatistic.Type != FunctionType.Cos && sinCosStatistic.Type != FunctionType.Sin))
                {
                    break;
                }

                if (multipliers.Count != 2 && multipliers.Count != 3)
                {
                    break;
                }

                isCan = true;
            }
            while (false);

            return isCan;
        }

        public Expression TryIntegrateMultipliers(List<Expression> multipliers, string variable)
        {
            multipliers.Sort((expression1, expression2) => expression1.Type.CompareTo(expression2.Type));
            
            FuncExpression expFunction = null;
            FuncExpression sinOrCosFunction = null;
            Expression otherExpression = null;
            foreach (var multiplier in multipliers)
            {
                var function = multiplier as FuncExpression;
                if (function == null)
                {
                    otherExpression = multiplier;
                }
                else if (function.FunctionType == FunctionType.Exp)
                {
                    expFunction = function;
                }
                else
                {
                    sinOrCosFunction = function;
                }
            }
            Expression result;
            if (otherExpression != null)
            {
                var integratedSinExpPart = Expression.Integrate(expFunction * sinOrCosFunction, variable, this);
                var firstPartOfResult = integratedSinExpPart * otherExpression;
                var secondPartOfResult = otherExpression.Differentiate(variable) * integratedSinExpPart;
                secondPartOfResult = Expression.Integrate(secondPartOfResult, variable, this);
                result = firstPartOfResult - secondPartOfResult;
            }
            else
            {
                Polinom sinCosPolinom = sinOrCosFunction.Argument.FormatToPolinom(variable);
                Polinom expPolinom = expFunction.Argument.FormatToPolinom(variable);
                var a = sinCosPolinom.GetConstantAt(1);
                var b = expPolinom.GetConstantAt(1);
                
                Expression firstAddend;
                Expression secondAddend;
                if (sinOrCosFunction.FunctionType == FunctionType.Cos)
                {
                    firstAddend = new SinFunction(sinOrCosFunction.Argument);
                    secondAddend = new CosFunction(sinOrCosFunction.Argument);
                }
                else
                {
                    firstAddend = new CosFunction(sinOrCosFunction.Argument).SetNegate(true);
                    secondAddend = new SinFunction(sinOrCosFunction.Argument);
                }

                result = expFunction*((a*firstAddend) + (b*secondAddend));
                result = result/(a*a + b*b);
            }
            return result;
        }
    }
}
